<?php
/**
 * @usage: 
		require_once(DIR_UTIL.'nonce.php');
		$user_ip = '10.50.21.4';
		$nonce = Nonce::generateString($user_ip);
		var_dump($nonce);
		var_dump(!Nonce::validateString($nonce, $user_ip));
		var_dump(!Nonce::checkReplayAttack($nonce, 20));
		var_dump(!Nonce::checkFlood('onlinedemo.taste', 60, $user_ip));
 */

class Nonce 
{
	const DEFAULT_TIME_INTERVAL = 3600;

    private static $cacher = NULL;
    private static $disableExpired = false;
	private static $secret = "htHxfIu1ZAsuMYOKpFT";//default secret key

    public static function generateString($id_token = NULL, $timestamp = NULL) 
	{
        return implode('.', array_values(self::generate($id_token, $timestamp)));
    }

    /**
     * @param string $string nonce string
     * @param string $id_token id_token used to create nonce 
     * @return bool if not correct true,else false
     *
     */
    public static function validateString($string, $id_token) 
	{
        $nonceData = self::parseNonceString($string);

        if (!$nonceData || count($nonceData) < 2) 
		{
            return false;
        }

        list($nonce, $created, $digest) = $nonceData;

        return self::validate($nonce, $created, $digest, $id_token) && self::checkExpired($created);
    }

    /**
     * @return if not attack return true, else false
     *
     */
    public static function checkReplayAttack($nonce, $time_interval = self::DEFAULT_TIME_INTERVAL)
	{
        return self::checkCacheKey($nonce, $time_interval);
    }

    /**
     * @return if not flood return true, else false
     *
     */
    public static function checkFlood($scope, $flood_interval = NULL, $user_id = 0, $max_count = 1)
	{
        $key   = strtolower($scope).'.'.$user_id;

        return self::checkCacheKey($key, $flood_interval, $max_count);
    }

    public static function checkAll($nonce, $nonce_created, $nonce_digest, $flood_scope, $flood_interval = NULL, $id_token = NULL, $time_interval = self::DEFAULT_TIME_INTERVAL)
	{
        return 
			self::validate($nonce, $nonce_created, $nonce_digest, $id_token)
            && self::checkExpired($nonce_created)
            && self::checkFlood($flood_scope, $flood_interval)
            && self::checkReplayAttack($nonce, $time_interval);
    }

    public static function checkAllString($string, $flood_scope, $flood_interval = NULL, $id_token = NULL, $time_interval = self::DEFAULT_TIME_INTERVAL) 
	{
        $nonceData = self::parseNonceString($string);

        if (!$nonceData)
		{
            return false;
        }

        list($nonce, $created, $digest) = $nonceData;
        
        return self::checkAll($nonce, $created, $digest, $flood_scope, $flood_interval, $id_token, $time_interval);
    }

	public static function setSecret($secret_key)
	{
		if (!$secret_key) 
		{
			return false;
		}

		self::$secret = $secret_key;

		return true;
	}

    public static function disableExpiredCheck() 
	{
        self::$disableExpired = true;
    }

	protected static function checkExpired($nonce_created, $time_interval = self::DEFAULT_TIME_INTERVAL)
	{
        if (self::$disableExpired == true) 
		{
            return true;
        }

        return ($nonce_created + $time_interval) > SC::get("board_config.time_now");
    }

	/**
	 * generate nonce string
	 */
	protected static function generate($id_token = NULL, $timestamp = NULL) 
	{
        if ($timestamp === NULL) 
		{
            $timestamp = SC::get("board_config.time_now");
        }

        $nonce  = md5($id_token.",".$timestamp);
        $digest = md5($nonce.",".$timestamp.",".self::$secret);

        $returnArray = array("nonce" => $nonce, "created" => $timestamp, "digest" => $digest);

        return $returnArray;
    }

    protected static function validate($nonce, $created, $digest, $id_token) 
	{
        if ($nonce == "") 
		{ 
			return false; 
		}

        if (($created < 0) || (!is_numeric($created))) 
		{ 
			return false; 
		}

        if ($digest == "") 
		{ 
			return false; 
		}

        // generate a nonce using the good 'ole process
        $nonceData = self::generate($id_token, $created);

        // verify nonce is a valid nonce
        if ($nonce != $nonceData["nonce"]) 
		{ 
			return false; 
		}

        // verify digest
        if ($nonceData["digest"] !== $digest) 
		{ 
			return false; 
		}

        return true;
    }

    protected static function parseNonceString($string)
	{
        //if( ! preg_match('/^[0-9]+\.[0-9]+\.[0-9]+$/', $string ) ) return FALSE;
        return explode('.', $string);
    }

    protected static function checkCacheKey($key, $time_interval, $max_count = 1)
	{
	    $time_interval = (int)$time_interval;
        $max_count     = (int)$max_count;

		if (1 == $max_count)
		{
			return self::checkCacheKeyNoCounter($key, $time_interval);
		}

		$count = self::cacher()->get($key);
	
        if (!$count)
		{
			self::cacher()->set($key, 1, NULL, $time_interval);
            return true;
        } 
		else 
		{
			if ($count >= $max_count)
			{
				return false;
			}
			else
			{
				$ret = self::cacher()->increment($key, 1);

				return true;
			}
        }
    }

    protected static function checkCacheKeyNoCounter($key, $time_interval)
	{
		$time_interval = (int)$time_interval;

        $ret = self::cacher()->add($key, 1, NULL, $time_interval);

        if ($ret)
		{
            return true;
        } 
		else 
		{
            $ret = self::cacher()->get($key);

            if ($ret !== false)
			{
                return false;
            } 
			else 
			{
                return true;
            }
        }
    }

    protected static function cacher()
	{
        if (self::$cacher) 
		{
			return self::$cacher;
		}

        return self::$cacher = MemcacheFactory::create('query_memcache');
    }

}//end class
